Navigating Change: Analyzing the Evolution of U.S. Manufacturing

The News Reported That…

A Big-Picture View of U.S. Manufacturing


Global Positioning

import pandas as pd
import altair as alt
import plotly.express as px
import json
import numpy as np
df = pd.read_csv("../data/raw/manufacturing-value-added-to-gdp.csv")
df = df.rename(columns={"Manufacturing, value added (% of GDP)": "ManufacturingGDP"})
df = df[(df['Year'] >= 2013) & (df['Year'] <= 2024)]

df = df.dropna(subset=['Code']).reset_index(drop=True)

# Create animated choropleth map
fig = px.choropleth(
    df,
    locations="Entity",
    locationmode="country names",   
    color="ManufacturingGDP",
    hover_name="Entity",
    animation_frame="Year",
    #color_continuous_scale="purples",
    color_continuous_scale=[
    (0.0, "#f2f0f7"),  # very light
    (0.2, "#cbc9e2"),  # light
    (0.4, "#9e9ac8"),  # mid
    (0.6, "#756bb1"),  # darker
    (0.8, "#54278f"),  # very dark
    (1.0, "#3f007d")   # darkest
],
    range_color=[0, 40],            
)

fig.update_layout(
    geo=dict(showframe=False, showcoastlines=False),
    coloraxis_colorbar=dict(title="% of GDP")
)

fig.show()

GDP Race: Manufacturing vs. other Sectors

import plotly.express as px
import pandas as pd
import numpy as np
import plotly.express as px
df = pd.read_csv("../data/Processed/GdpByInd.csv")
# Clean up column names just in case
df.columns = df.columns.str.strip()

# Identify actual year columns (assuming they are 4-digit years)
year_columns = [col for col in df.columns if col.isdigit() and len(col) == 4]

# Melt the data safely
df_long = df.melt(
    id_vars=["Group", "Subgroup"],
    value_vars=year_columns,
    var_name="Year",
    value_name="Value"
)

# Convert year to integer
df_long["Year"] = df_long["Year"].astype(int)

df_grouped = df_long.groupby(["Group", "Year"], as_index=False)["Value"].sum()

unique_groups = df_grouped["Group"].unique()
group_positions = {
    group: (np.cos(i * 2 * np.pi / len(unique_groups)) * 3,
            np.sin(i * 2 * np.pi / len(unique_groups)) * 3)
    for i, group in enumerate(unique_groups)
}

# Filter for 2013–2024
df_grouped_filtered = df_grouped[df_grouped["Year"].between(2013, 2024)].copy()

# Convert from millions to billions
df_grouped_filtered["Value_Trillions"] = df_grouped_filtered["Value"] / 1000000

# Step 2: Add a column for custom coloring
df_grouped_filtered["Color"] = df_grouped_filtered["Group"].apply(
    lambda g: "Manufacturing" if g == "Manufacturing" else "Other"
)

# Step 3: Define color map (blue for Manufacturing, gray for others)
color_map = {
    "Manufacturing": "#28293D",
    "Other": "#9997bc"
}

fig = px.bar(
    df_grouped_filtered,
    x="Value_Trillions",
    y="Group",
    color="Color",
    animation_frame="Year",
    orientation='h',
    color_discrete_map=color_map,
    hover_data={"Color": False}  
)

# Set custom tooltip on static traces (initial frame)
for trace in fig.data:
    trace.hovertemplate = "<b>%{y}</b><br>Value (Trillion $): %{x:.2f}<extra></extra>"

# Set custom tooltip on animated frames
for frame in fig.frames:
    for trace in frame.data:
        trace.hovertemplate = "<b>%{y}</b><br>Value (Trillion $): %{x:.2f}<extra></extra>"

fig.update_layout(
    showlegend=False,
    xaxis=dict(
        title="Output (Trillions $)",
        tickformat=".2f",
        range=[0, 12.5],
        showgrid=False,  # Turn off vertical grid lines
        zeroline=False   # Turn off the thick zero line
    ),
    yaxis=dict(
        title = "Sectors",
        showgrid=False,  # Turn off horizontal grid lines
        zeroline=False
    ),
    plot_bgcolor='white',   # Plot area background
    paper_bgcolor='white',  # Entire figure background
    margin=dict(t=40, l=100, r=40, b=40)
)

fig.show()

Employment Race: Manufacturing vs. other Sectors

import pandas as pd
import altair as alt
import ipywidgets as widgets
from IPython.display import display
from ipywidgets import VBox

# Read data
df_clean = pd.read_csv("../data/Processed/employment_rate.csv")
df_clean['Month'] = pd.to_datetime(df_clean['Month'], errors='coerce')

# Original industries to plot
industries_to_plot = [
    'Manufacturing',
    'Construction',
    'Retail trade',
    'Transportation and warehousing',
    'Leisure and hospitality',
    'Financial activities'
]

# Mapping of original industry names to new legend labels
legend_labels = {
    'Manufacturing': 'Manufacturing',
    'Construction': 'Utilities & Construction',
    'Retail trade': 'Retail trade',
    'Transportation and warehousing': 'Transportation and warehousing',
    'Leisure and hospitality': 'Health, Education & Leisure',
    'Financial activities': 'Information & Finance'
}

# Melt data to long format
df_long = df_clean.melt(
    id_vars=['Month'],
    value_vars=industries_to_plot,
    var_name='Industry',
    value_name='Employment'
)

# Add custom legend labels based on the mapping
df_long['Industry_legend'] = df_long['Industry'].map(legend_labels)

# Add stack order: higher number = stacked higher (Manufacturing on top)
stack_order = {
    'Manufacturing': 0,
    'Utilities & Construction': 1,
    'Retail trade': 2,
    'Transportation and warehousing': 3,
    'Health, Education & Leisure': 4,
    'Information & Finance': 5
}

df_long['stack_order'] = df_long['Industry_legend'].map(stack_order)

# Selection widgets (not actively filtering the chart here, but kept for future interactivity)
start_picker = widgets.SelectionSlider(
    options=list(df_clean['Month'].dt.to_period('M').astype(str)),
    description='Start Month',
    layout=widgets.Layout(width='800px')
)

end_picker = widgets.SelectionSlider(
    options=list(df_clean['Month'].dt.to_period('M').astype(str)),
    description='End Month',
    layout=widgets.Layout(width='800px')
)

# Explicit industry order
ordered_industries = [
    'Manufacturing',
    'Utilities & Construction',
    'Retail trade',
    'Transportation and warehousing',
    'Health, Education & Leisure',
    'Information & Finance'
]

# Create stacked area chart
area_stack_chart = alt.Chart(df_long).mark_area(opacity=0.8).encode(
    x=alt.X('Month:T',
            title='Year',
            axis=alt.Axis(
                format='%Y',
                grid=False,
                labelExpr="timeFormat(datum.value, '%m') == '01' ? timeFormat(datum.value, '%Y') : ''"
            )),
    y=alt.Y('Employment:Q',
            stack='zero',
            title='Employment (Thousands)',
            axis=alt.Axis(grid=False)),
    color=alt.Color(
        'Industry_legend:N',
        scale=alt.Scale(
            domain=ordered_industries,
            range=['#3f007d', '#54278f', '#756bb1', '#9e9ac8', '#cbc9e2', '#f2f0f7']
  # Use built-in purples palette
        )
    ),
    order=alt.Order('stack_order:Q'),
    tooltip=[
        alt.Tooltip('Month:T', title='Year', format='%Y'),
        alt.Tooltip('Month:T', title='Quarter', timeUnit='quarter'),
        alt.Tooltip('Industry_legend:N', title='Industry'),
        alt.Tooltip('Employment:Q', title='Employment')
    ]
).properties(
    width='container',
    height=400,
    title='Stacked Employment Trend by Industry (2013–2023)'
)

# Legend styling
area_stack_chart = area_stack_chart.configure_legend(
    orient='top',
    direction='horizontal',
    title=None,
    offset=40,
    padding=20,
    labelFontSize=12
)

# Display the chart
display(area_stack_chart)

Conclusion


Forces Shaped a Recovery that is Real, But Uneven …

  • Market Dynamics — imported goods have become significantly cheaper than domestic ones, capturing a growing share of the market.
  • Policy and Enterprise Developments — while federal initiatives sparked short-term growth, structural issues like high costs and slow productivity gains limited long-term impact.
  • Workforce Conditions — rising wages and volatile employment trends have made labor costs harder to manage.

Thank You